<!-- filepath: src/components/LiquidGlass.vue -->
<script setup lang="ts">
import { ref, reactive, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'
import { ShaderDisplacementGenerator, fragmentShaders } from '../libs/shader-utils'
import { displacementMap, polarDisplacementMap, prominentDisplacementMap } from '../libs/utils'

const props = defineProps({
  displacementScale: { type: Number, default: 70 },
  blurAmount: { type: Number, default: 0.0625 },
  saturation: { type: Number, default: 140 },
  aberrationIntensity: { type: Number, default: 2 },
  elasticity: { type: Number, default: 0.15 },
  cornerRadius: { type: Number, default: 999 },
  globalMousePos: { type: Object as () => { x: number, y: number }, default: () => ({ x: 0, y: 0 }) },
  mouseOffset: { type: Object as () => { x: number, y: number }, default: () => ({ x: 0, y: 0 }) },
  mouseContainer: { type: Object, default: null },
  className: { type: String, default: '' },
  padding: { type: String, default: '24px 32px' },
  overLight: { type: Boolean, default: false },
  style: { type: Object, default: () => ({}) },
  mode: { type: String as () => 'standard' | 'polar' | 'prominent' | 'shader', default: 'standard' },
  onClick: { type: Function, default: null }
})

const glassRef = ref<HTMLElement | null>(null)
const isHovered = ref(false)
const isActive = ref(false)
const glassSize = reactive<{ width: number, height: number }>({ width: 270, height: 69 })
const internalGlobalMousePos = reactive({ x: 0, y: 0 })
const internalMouseOffset = reactive({ x: 0, y: 0 })
const shaderMapUrl = ref('')

// 生成 shader displacement map
function generateShaderDisplacementMap(width: number, height: number): string {
  const generator = new ShaderDisplacementGenerator({
    width,
    height,
    fragment: fragmentShaders.liquidGlass,
  })
  const dataUrl = generator.updateShader()
  generator.destroy()
  return dataUrl
}

function getMap(mode: string, shaderMapUrl?: string) {
  switch (mode) {
    case 'standard': return displacementMap
    case 'polar': return polarDisplacementMap
    case 'prominent': return prominentDisplacementMap
    case 'shader': return shaderMapUrl || displacementMap
    default: throw new Error(`Invalid mode: ${mode}`)
  }
}

// 监听 mode 或尺寸变化时生成 shaderMap
watch(
  () => [props.mode, glassSize.width, glassSize.height],
  ([mode, w, h]) => {
    if (mode === 'shader') {
      shaderMapUrl.value = generateShaderDisplacementMap(Number(w), Number(h))
    }
  },
  { immediate: true }
)

// 监听窗口尺寸变化，更新 glassSize
function updateGlassSize() {
  if (glassRef.value) {
    const rect = glassRef.value.getBoundingClientRect()
    glassSize.width = rect.width
    glassSize.height = rect.height
  }
}
onMounted(() => {
  nextTick(updateGlassSize)
  window.addEventListener('resize', updateGlassSize)
})
onUnmounted(() => {
  window.removeEventListener('resize', updateGlassSize)
})

// 鼠标跟踪
function handleMouseMove(e: MouseEvent) {
  const container = props.mouseContainer?.value || glassRef.value
  if (!container) return
  const rect = container.getBoundingClientRect()
  const centerX = rect.left + rect.width / 2
  const centerY = rect.top + rect.height / 2
  internalMouseOffset.x = ((e.clientX - centerX) / rect.width) * 100
  internalMouseOffset.y = ((e.clientY - centerY) / rect.height) * 100
  internalGlobalMousePos.x = e.clientX
  internalGlobalMousePos.y = e.clientY
}
onMounted(() => {
  if (!props.globalMousePos.x && !props.globalMousePos.y) {
    const container = props.mouseContainer?.value || glassRef.value
    container?.addEventListener('mousemove', handleMouseMove)
  }
})
onUnmounted(() => {
  const container = props.mouseContainer?.value || glassRef.value
  container?.removeEventListener('mousemove', handleMouseMove)
})

const globalMousePos = computed(() => props.globalMousePos.x ? props.globalMousePos : internalGlobalMousePos)
const mouseOffset = computed(() => props.mouseOffset.x ? props.mouseOffset : internalMouseOffset)

// 计算缩放和弹性
function calculateDirectionalScale() {
  const { x, y } = globalMousePos.value
  if (!x || !y || !glassRef.value) return 'scale(1)'
  const rect = glassRef.value.getBoundingClientRect()
  const pillCenterX = rect.left + rect.width / 2
  const pillCenterY = rect.top + rect.height / 2
  const pillWidth = glassSize.width
  const pillHeight = glassSize.height
  const deltaX = x - pillCenterX
  const deltaY = y - pillCenterY
  const edgeDistanceX = Math.max(0, Math.abs(deltaX) - pillWidth / 2)
  const edgeDistanceY = Math.max(0, Math.abs(deltaY) - pillHeight / 2)
  const edgeDistance = Math.sqrt(edgeDistanceX ** 2 + edgeDistanceY ** 2)
  const activationZone = 200
  if (edgeDistance > activationZone) return 'scale(1)'
  const fadeInFactor = 1 - edgeDistance / activationZone
  const centerDistance = Math.sqrt(deltaX ** 2 + deltaY ** 2)
  if (centerDistance === 0) return 'scale(1)'
  const normalizedX = deltaX / centerDistance
  const normalizedY = deltaY / centerDistance
  const stretchIntensity = Math.min(centerDistance / 300, 1) * props.elasticity * fadeInFactor
  const scaleX = 1 + Math.abs(normalizedX) * stretchIntensity * 0.3 - Math.abs(normalizedY) * stretchIntensity * 0.15
  const scaleY = 1 + Math.abs(normalizedY) * stretchIntensity * 0.3 - Math.abs(normalizedX) * stretchIntensity * 0.15
  return `scaleX(${Math.max(0.8, scaleX)}) scaleY(${Math.max(0.8, scaleY)})`
}
function calculateFadeInFactor() {
  const { x, y } = globalMousePos.value
  if (!x || !y || !glassRef.value) return 0
  const rect = glassRef.value.getBoundingClientRect()
  const pillCenterX = rect.left + rect.width / 2
  const pillCenterY = rect.top + rect.height / 2
  const pillWidth = glassSize.width
  const pillHeight = glassSize.height
  const edgeDistanceX = Math.max(0, Math.abs(x - pillCenterX) - pillWidth / 2)
  const edgeDistanceY = Math.max(0, Math.abs(y - pillCenterY) - pillHeight / 2)
  const edgeDistance = Math.sqrt(edgeDistanceX ** 2 + edgeDistanceY ** 2)
  const activationZone = 200
  return edgeDistance > activationZone ? 0 : 1 - edgeDistance / activationZone
}
function calculateElasticTranslation() {
  if (!glassRef.value) return { x: 0, y: 0 }
  const fadeInFactor = calculateFadeInFactor()
  const rect = glassRef.value.getBoundingClientRect()
  const pillCenterX = rect.left + rect.width / 2
  const pillCenterY = rect.top + rect.height / 2
  return {
    x: (globalMousePos.value.x - pillCenterX) * props.elasticity * 0.1 * fadeInFactor,
    y: (globalMousePos.value.y - pillCenterY) * props.elasticity * 0.1 * fadeInFactor,
  }
}

const transformStyle = computed(() => {
  const elastic = calculateElasticTranslation()
  return `translate(calc(-50% + ${elastic.x}px), calc(-50% + ${elastic.y}px)) ${isActive.value && !!props.onClick ? 'scale(0.96)' : calculateDirectionalScale()}`
})

const baseStyle = computed(() => ({
  ...props.style,
  transform: transformStyle.value,
  transition: 'all ease-out 0.2s'
}))
const positionStyles = computed(() => ({
  position: props.style?.position || 'relative',
  top: props.style?.top || '50%',
  left: props.style?.left || '50%'
}))

const isFirefox = navigator.userAgent.toLowerCase().includes('firefox')
const backdropStyle = computed(() => ({
  filter: isFirefox ? null : `url(#glass-filter)`,
  backdropFilter: `blur(${(props.overLight ? 12 : 4) + props.blurAmount * 32}px) saturate(${props.saturation}%)`
}))
</script>

<template>
  <!-- over light effect -->
  <div class="bg-black transition-all duration-150 ease-in-out pointer-events-none"
    :class="props.overLight ? 'opacity-20' : 'opacity-0'" :style="{
      ...positionStyles,
      height: glassSize.height + 'px',
      width: glassSize.width + 'px',
      borderRadius: props.cornerRadius + 'px',
      transform: baseStyle.transform,
      transition: baseStyle.transition
    }" />
  <div class="bg-black transition-all duration-150 ease-in-out pointer-events-none mix-blend-overlay"
    :class="props.overLight ? 'opacity-100' : 'opacity-0'" :style="{
      ...positionStyles,
      height: glassSize.height + 'px',
      width: glassSize.width + 'px',
      borderRadius: props.cornerRadius + 'px',
      transform: baseStyle.transform,
      transition: baseStyle.transition
    }" />

  <!-- Glass Container -->
  <div ref="glassRef" :class="['relative', props.className, { active: isActive, 'cursor-pointer': !!props.onClick }]"
    :style="baseStyle" @mouseenter="isHovered = true" @mouseleave="isHovered = false" @mousedown="isActive = true"
    @mouseup="isActive = false" @click="props.onClick && props.onClick()">
    <!-- SVG Filter -->
    <svg style="position: absolute;" :width="glassSize.width" :height="glassSize.height" aria-hidden="true">
      <defs>
        <radialGradient id="glass-edge-mask" cx="50%" cy="50%" r="50%">
          <stop offset="0%" stop-color="black" stop-opacity="0" />
          <stop :offset="Math.max(30, 80 - props.aberrationIntensity * 2) + '%'" stop-color="black" stop-opacity="0" />
          <stop offset="100%" stop-color="white" stop-opacity="1" />
        </radialGradient>
        <filter id="glass-filter" x="-35%" y="-35%" width="170%" height="170%" color-interpolation-filters="sRGB">
          <feImage x="0" y="0" width="100%" height="100%" result="DISPLACEMENT_MAP"
            :href="getMap(props.mode, shaderMapUrl)" preserveAspectRatio="xMidYMid slice" />
          <feColorMatrix in="DISPLACEMENT_MAP" type="matrix" values="0.3 0.3 0.3 0 0
                    0.3 0.3 0.3 0 0
                    0.3 0.3 0.3 0 0
                    0 0 0 1 0" result="EDGE_INTENSITY" />
          <feComponentTransfer in="EDGE_INTENSITY" result="EDGE_MASK">
            <feFuncA type="discrete" :tableValues="`0 ${props.aberrationIntensity * 0.05} 1`" />
          </feComponentTransfer>
          <feOffset in="SourceGraphic" dx="0" dy="0" result="CENTER_ORIGINAL" />
          <feDisplacementMap in="SourceGraphic" in2="DISPLACEMENT_MAP"
            :scale="props.displacementScale * (props.mode === 'shader' ? 1 : -1)" xChannelSelector="R"
            yChannelSelector="B" result="RED_DISPLACED" />
          <feColorMatrix in="RED_DISPLACED" type="matrix" values="1 0 0 0 0
                    0 0 0 0 0
                    0 0 0 0 0
                    0 0 0 1 0" result="RED_CHANNEL" />
          <feDisplacementMap in="SourceGraphic" in2="DISPLACEMENT_MAP"
            :scale="props.displacementScale * ((props.mode === 'shader' ? 1 : -1) - props.aberrationIntensity * 0.05)"
            xChannelSelector="R" yChannelSelector="B" result="GREEN_DISPLACED" />
          <feColorMatrix in="GREEN_DISPLACED" type="matrix" values="0 0 0 0 0
                    0 1 0 0 0
                    0 0 0 0 0
                    0 0 0 1 0" result="GREEN_CHANNEL" />
          <feDisplacementMap in="SourceGraphic" in2="DISPLACEMENT_MAP"
            :scale="props.displacementScale * ((props.mode === 'shader' ? 1 : -1) - props.aberrationIntensity * 0.1)"
            xChannelSelector="R" yChannelSelector="B" result="BLUE_DISPLACED" />
          <feColorMatrix in="BLUE_DISPLACED" type="matrix" values="0 0 0 0 0
                    0 0 0 0 0
                    0 0 1 0 0
                    0 0 0 1 0" result="BLUE_CHANNEL" />
          <feBlend in="GREEN_CHANNEL" in2="BLUE_CHANNEL" mode="screen" result="GB_COMBINED" />
          <feBlend in="RED_CHANNEL" in2="GB_COMBINED" mode="screen" result="RGB_COMBINED" />
          <feGaussianBlur in="RGB_COMBINED" :stdDeviation="Math.max(0.1, 0.5 - props.aberrationIntensity * 0.1)"
            result="ABERRATED_BLURRED" />
          <feComposite in="ABERRATED_BLURRED" in2="EDGE_MASK" operator="in" result="EDGE_ABERRATION" />
          <feComponentTransfer in="EDGE_MASK" result="INVERTED_MASK">
            <feFuncA type="table" tableValues="1 0" />
          </feComponentTransfer>
          <feComposite in="CENTER_ORIGINAL" in2="INVERTED_MASK" operator="in" result="CENTER_CLEAN" />
          <feComposite in="EDGE_ABERRATION" in2="CENTER_CLEAN" operator="over" />
        </filter>
      </defs>
    </svg>

    <div class="glass" :style="{
      borderRadius: props.cornerRadius + 'px',
      position: 'relative',
      display: 'inline-flex',
      alignItems: 'center',
      gap: '24px',
      padding: props.padding,
      overflow: 'hidden',
      transition: 'all 0.2s ease-in-out',
      boxShadow: props.overLight
        ? '0px 16px 70px rgba(0, 0, 0, 0.75)'
        : '0px 12px 40px rgba(0, 0, 0, 0.25)'
    }">
      <span class="glass__warp" :style="{
        position: 'absolute',
        inset: 0,
        filter: backdropStyle.filter || 'unset',
        backdropFilter: backdropStyle.backdropFilter,
      }" />
      <div class="transition-all duration-150 ease-in-out text-white" :style="{
        position: 'relative',
        zIndex: 1,
        font: '500 20px/1 system-ui',
        textShadow: props.overLight
          ? '0px 2px 12px rgba(0, 0, 0, 0)'
          : '0px 2px 12px rgba(0, 0, 0, 0.4)'
      }">
        <slot />
      </div>
    </div>
  </div>

  <!-- 边框和高亮层 -->
  <span :style="{
    ...positionStyles,
    height: glassSize.height + 'px',
    width: glassSize.width + 'px',
    borderRadius: props.cornerRadius + 'px',
    transform: baseStyle.transform,
    transition: baseStyle.transition,
    pointerEvents: 'none',
    mixBlendMode: 'screen',
    opacity: 0.2,
    padding: '1.5px',
    WebkitMask: 'linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0)',
    WebkitMaskComposite: 'xor',
    maskComposite: 'exclude',
    boxShadow: '0 0 0 0.5px rgba(255,255,255,0.5) inset, 0 1px 3px rgba(255,255,255,0.25) inset, 0 1px 4px rgba(0,0,0,0.35)',
    background: `linear-gradient(
        ${135 + mouseOffset.x * 1.2}deg,
        rgba(255,255,255,0.0) 0%,
        rgba(255,255,255,${0.12 + Math.abs(mouseOffset.x) * 0.008}) ${Math.max(10, 33 + mouseOffset.y * 0.3)}%,
        rgba(255,255,255,${0.4 + Math.abs(mouseOffset.x) * 0.012}) ${Math.min(90, 66 + mouseOffset.y * 0.4)}%,
        rgba(255,255,255,0.0) 100%
      )`
  }" />
  <span :style="{
    ...positionStyles,
    height: glassSize.height + 'px',
    width: glassSize.width + 'px',
    borderRadius: props.cornerRadius + 'px',
    transform: baseStyle.transform,
    transition: baseStyle.transition,
    pointerEvents: 'none',
    mixBlendMode: 'overlay',
    padding: '1.5px',
    WebkitMask: 'linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0)',
    WebkitMaskComposite: 'xor',
    maskComposite: 'exclude',
    boxShadow: '0 0 0 0.5px rgba(255,255,255,0.5) inset, 0 1px 3px rgba(255,255,255,0.25) inset, 0 1px 4px rgba(0,0,0,0.35)',
    background: `linear-gradient(
        ${135 + mouseOffset.x * 1.2}deg,
        rgba(255,255,255,0.0) 0%,
        rgba(255,255,255,${0.32 + Math.abs(mouseOffset.x) * 0.008}) ${Math.max(10, 33 + mouseOffset.y * 0.3)}%,
        rgba(255,255,255,${0.6 + Math.abs(mouseOffset.x) * 0.012}) ${Math.min(90, 66 + mouseOffset.y * 0.4)}%,
        rgba(255,255,255,0.0) 100%
      )`
  }" />

  <!-- Hover/Active 效果 -->
  <template v-if="props.onClick">
    <div :style="{
      ...positionStyles,
      height: glassSize.height + 'px',
      width: glassSize.width + 1 + 'px',
      borderRadius: props.cornerRadius + 'px',
      transform: baseStyle.transform,
      pointerEvents: 'none',
      transition: 'all 0.2s ease-out',
      opacity: isHovered || isActive ? 0.5 : 0,
      backgroundImage: 'radial-gradient(circle at 50% 0%, rgba(255,255,255,0.5) 0%, rgba(255,255,255,0) 50%)',
      mixBlendMode: 'overlay'
    }" />
    <div :style="{
      ...positionStyles,
      height: glassSize.height + 'px',
      width: glassSize.width + 1 + 'px',
      borderRadius: props.cornerRadius + 'px',
      transform: baseStyle.transform,
      pointerEvents: 'none',
      transition: 'all 0.2s ease-out',
      opacity: isActive ? 0.5 : 0,
      backgroundImage: 'radial-gradient(circle at 50% 0%, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 80%)',
      mixBlendMode: 'overlay'
    }" />
    <div :style="{
      ...baseStyle,
      height: glassSize.height + 'px',
      width: glassSize.width + 1 + 'px',
      borderRadius: props.cornerRadius + 'px',
      position: style.position,
      top: style.top,
      left: style.left,
      pointerEvents: 'none',
      transition: 'all 0.2s ease-out',
      opacity: isHovered ? 0.4 : isActive ? 0.8 : 0,
      backgroundImage: 'radial-gradient(circle at 50% 0%, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 100%)',
      mixBlendMode: 'overlay'
    }" />
  </template>
</template>